package com.blog4j.server.common.aop;

import com.blog4j.common.annotation.SmsIpDayLimit;
import com.blog4j.common.constants.CacheConstants;
import com.blog4j.common.enums.ErrorEnum;
import com.blog4j.common.enums.SmsTypeEnum;
import com.blog4j.common.exception.Blog4jException;
import com.blog4j.common.utils.RealIpUtil;
import com.blog4j.common.utils.RedisUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.time.Duration;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.Objects;

/**
 * @author 98k灬
 * @version v1.0.0
 * @Description : 功能描述
 * @Create on : 2024/8/29 21:03
 **/
@Slf4j
@RequiredArgsConstructor(onConstructor_ = @Autowired)
@Component
@Aspect
public class SmsDayLimitAspect {
    private static final String PHONE = "phone";

    private final RedisUtil redisUtil;

    @Pointcut("@annotation(com.blog4j.common.annotation.SmsIpDayLimit)")
    public void smsDayLimit() {}

    @AfterReturning("smsDayLimit()")
    public void doAfterReturning(JoinPoint joinPoint) {
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        SmsIpDayLimit annotation = method.getAnnotation(SmsIpDayLimit.class);
        if (Objects.isNull(annotation)) {
            return;
        }

        SmsTypeEnum smsType = annotation.smsType();
        String realIp = RealIpUtil.getRealIp(request);
        String phone = request.getParameter(PHONE);
        if (smsType == SmsTypeEnum.IP) {
            checkSmsIpDayLimit(annotation, realIp);
        } else {
            checkSmsPhoneDayLimit(annotation, phone);
        }
    }

    private void checkSmsPhoneDayLimit(SmsIpDayLimit annotation, String phone) {
        int phoneTimes = annotation.phoneTimes();
        String cacheKey = CacheConstants.SMS_PHONE_DAY_LIMIT_KEY + phone;
        Object val = redisUtil.get(cacheKey);
        if (Objects.isNull(val)) {
            redisUtil.set(cacheKey, String.valueOf(1), getTimeIntervalFromZero());
            return;
        }
        int cachePhoneTimes = Integer.parseInt(val.toString());
        if (cachePhoneTimes >= phoneTimes) {
            log.error("每个手机号码每天发送短信的次数超出上限, phone: [{}], phoneTimes: [{}], allowPhoneTimes: [{}]",
                    phone, cachePhoneTimes, phoneTimes);
            throw new Blog4jException(ErrorEnum.SMS_PHONE_DAY_LIMIT_ERROR);
        }
        redisUtil.incrOne(cacheKey);
    }

    private void checkSmsIpDayLimit(SmsIpDayLimit annotation, String realIp) {
        int ipTimes = annotation.ipTimes();
        String cacheKey = CacheConstants.SMS_IP_DAY_LIMIT_KEY + realIp;
        Object val = redisUtil.get(cacheKey);
        if (Objects.isNull(val)) {
            redisUtil.set(cacheKey, String.valueOf(1), getTimeIntervalFromZero());
            return;
        }
        int cacheIpTimes = Integer.parseInt(val.toString());
        if (cacheIpTimes >= ipTimes) {
            log.error("每个IP每天发送短信的次数超出上限, ip: [{}], ipTimes: [{}], allowIpTime: [{}]",
                    realIp, cacheIpTimes, ipTimes);
            throw new Blog4jException(ErrorEnum.SMS_IP_DAY_LIMIT_ERROR);
        }
        redisUtil.incrOne(cacheKey);
    }

    private long getTimeIntervalFromZero() {
        LocalDateTime now = LocalDateTime.now();
        LocalDateTime zeroDateTime = now.with(LocalTime.MIDNIGHT);
        Duration duration = Duration.between(zeroDateTime, now);
        return duration.getSeconds();
    }
}
